package com.zqq.demo.camera1;

import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.Bundle;
import android.os.Environment;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.zqq.demo.R;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;

public class Camera2Activity extends AppCompatActivity implements SurfaceHolder.Callback, Camera.PreviewCallback {
    
    private Camera mCamera;
    private SurfaceView mSurfaceView;
    private MediaCodec mEncoder;
    private MediaMuxer mMuxer;
    private int mTrackIndex;
    private boolean mMuxerStarted;
    private String filePathH264;
    private String filePathMp4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_preview);

        File file = new File( getExternalCacheDir()+"/videos");
        if(!file.exists()){
            file.mkdirs();
        }
        File h264 = new File(file,System.currentTimeMillis()+".h264");
        if(!h264.exists()){
            try {
                h264.createNewFile();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        filePathH264 = h264.getPath();

        File mp4 = new File(file,System.currentTimeMillis()+".mp4");
        if(!mp4.exists()){
            try {
                mp4.createNewFile();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        filePathMp4 = mp4.getPath();

        mSurfaceView = findViewById(R.id.surface_view);
        mSurfaceView.getHolder().addCallback(this);
        
        try {
            initMuxer(filePathMp4);
            initEncoder();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initMuxer(String outputPath) throws IOException {
        mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        mMuxerStarted = false;
    }

    private void initEncoder() throws IOException {
        mEncoder = MediaCodec.createEncoderByType("video/avc");
        MediaFormat format = MediaFormat.createVideoFormat("video/avc", 1280, 720);
        format.setInteger(MediaFormat.KEY_BIT_RATE, 200000);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mEncoder.start();
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        encodeFrame(data);
        camera.addCallbackBuffer(data);
    }

    private void encodeFrame(byte[] yuvData) {
        try {
            ByteBuffer[] inputBuffers = mEncoder.getInputBuffers();
            int inputBufferIndex = mEncoder.dequeueInputBuffer(10000);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();
                inputBuffer.put(yuvData);
                mEncoder.queueInputBuffer(inputBufferIndex, 0, yuvData.length, 
                    System.nanoTime() / 1000, 0);
            }
            
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 10000);
            while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);
                byte[] outData = new byte[bufferInfo.size];
                outputBuffer.get(outData);
                
                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                    if (!mMuxerStarted) {
                        MediaFormat format = mEncoder.getOutputFormat();
                        mTrackIndex = mMuxer.addTrack(format);
                        mMuxer.start();
                        mMuxerStarted = true;
                    }
                } else {
                    mMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo);
                }
                
                mEncoder.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            mCamera = Camera.open();
            Camera.Parameters params = mCamera.getParameters();
            params.setPreviewSize(1280, 720);
            params.setPreviewFormat(ImageFormat.NV21);
            mCamera.setParameters(params);
            mCamera.setPreviewDisplay(holder);
            mCamera.setPreviewCallbackWithBuffer(this);
            
            byte[] buffer = new byte[1280 * 720 * 3 / 2];
            mCamera.addCallbackBuffer(buffer);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
        mMuxerStarted = false;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
        }
        if (mEncoder != null) {
            mEncoder.stop();
            mEncoder.release();
        }
        if (mMuxer != null) {
            mMuxer.stop();
            mMuxer.release();
        }
    }
}
